<?php

namespace Upfor\UpforPHP\Library;

use Throwable;

/**
 * IP utilities
 */
class IP {

    /**
     * Checks if an IP is in the specified range
     *
     * @param  string $ip    IP address
     * @param  string $range Ip address or IP range
     * @return boolean
     */
    public static function inRange($ip, $range) {
        return strpos($ip, '.') === false ? self::inRangeIPv6($ip, $range) : self::inRangeIPv4($ip, $range);
    }

    /**
     * Checks if an IP is in the specified range(IPv4)
     *
     * @param  string $ip    IP address
     * @param  string $range Ip address or IP range
     * @return boolean
     */
    public static function inRangeIPv4($ip, $range) {
        if (strpos($range, '/') === false) {
            $netMask = 32;
        } else {
            list($range, $netMask) = explode('/', $range, 2);
            if ($netMask < 0 || $netMask > 32) {
                return false;
            }
        }
        if (($ip2Long = ip2long($ip)) === false || ($range2Long = ip2long($range)) === false) {
            return false;
        }
        $netMaskDecimal = ~(pow(2, (32 - $netMask)) - 1);

        return ($ip2Long & $netMaskDecimal) === ($range2Long & $netMaskDecimal);
    }

    /**
     * Checks if an IP is in the specified range(IPv6)
     *
     * @param  string $ip    IP address
     * @param  string $range Ip address or IP range
     * @return boolean
     */
    public static function inRangeIPv6($ip, $range) {
        if (strpos($range, '/') === false) {
            $netMask = 128;
        } else {
            list($range, $netMask) = explode('/', $range, 2);
            if ($netMask < 1 || $netMask > 128) {
                return false;
            }
        }
        $binNetMask = str_repeat("f", $netMask / 4);
        switch ($netMask % 4) {
            case 1:
                $binNetMask .= "8";
                break;
            case 2:
                $binNetMask .= "c";
                break;
            case 3:
                $binNetMask .= "e";
                break;
        }
        $binNetMask = pack("H*", str_pad($binNetMask, 32, '0'));
        try {
            return (inet_pton($ip) & $binNetMask) === inet_pton($range);
        } catch (Throwable $e) {
            return false;
        }
    }

}
